package com.fjec.common.ys;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fjec.common.Const;
import com.fjec.common.okhttp.OkHttp;
import com.fjec.common.util.Base64Util;
import com.fjec.common.util.DESUtil;
import com.fjec.common.util.DateUtil;
import com.fjec.common.util.JsonUtil;

import com.fjec.common.ys.protocol.*;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.MDC;

import java.io.ByteArrayInputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

@Slf4j
public class PaidHelper {

    private OkHttp okHttp;

    private YsProperties ysProperties;

    public PaidHelper(OkHttp okHttp, YsProperties ysProperties) {
        this.okHttp = okHttp;
        this.ysProperties = ysProperties;
    }

    // 获取预支付信息
    public Map<String, Object> wxPreparePaid(String subOpenId, String outTradeNo, String shopDate, String subject,
                                      String totalAmount, String sellerId, String sellerName, Integer paymentType) throws Exception {
        String url = "https://qrcode.ysepay.com/gateway.do";
        // 创建业务参数
        Map<String, Object> bizParamsMap = new HashMap<>();
        bizParamsMap.put("out_trade_no", outTradeNo); // String(1,32)	商户生成的订单号，生成规则前8位必须为交易日期，如20180525，范围跨度支持包含当天在内的前后一天，且只能由大小写英文字母、数字、下划线及横杠组成
        bizParamsMap.put("shopdate", shopDate); // String(8,8) 商户日期(该参数做交易与查询时需要一致) 该日期需在当日的前后一天时间范围之内
        bizParamsMap.put("subject", subject); // String(1,250) 商品的标题/交易标题/订单标题/订单关键字等。该参数最长为250个汉字。
        bizParamsMap.put("total_amount", totalAmount); // Number(10,2) 该笔订单的资金总额，单位为RMB-Yuan。取值范围为[0.01，100000000.00]，精确到小数点后两位。Number(10,2)指10位长度，2位精度
        bizParamsMap.put("currency", "CNY"); // String(3) 支持币种：CNY（人民币）、HKD（港币）、USD（美元）、EUR（欧元）、JPY（日元）。注：不填默认是CNY（人民币），如是非跨境商户，币种不能填HKD（港币）、USD（美元）、EUR（欧元）、JPY（日元）中的任意一种外币币种。
        bizParamsMap.put("seller_id", sellerId); // String(1,20) 收款方银盛支付用户号
        bizParamsMap.put("seller_name", sellerName); // String(1,50) 收款方银盛支付客户名
        bizParamsMap.put("timeout_express", "1h"); // String(2,6) 设置未付款交易的超时时间，一旦超时，该笔交易就会自动被关闭。(需申请业务权限，权限未开通情况下该参数不生效，默认未付款交易的超时时间为7d)。取值范围：1m～15d。m-分钟，h-小时，d-天。该参数数值不接受小数点，如1.5h，可转换为90m。注意：设置了未付款交易超时时间的情况下，若我司在限定时间内没有收到成功支付通知，则会关闭交易，关闭后该笔交易若付款方支付成功的情况下，会自动原路退款至付款方。
        bizParamsMap.put("business_code", ysProperties.getBusinessCode()); // String(1,10) 业务代码 business_code  固定 00510030
        bizParamsMap.put("sub_openid", subOpenId); // String(128) 微信用户所关注商家公众号的 openid
        bizParamsMap.put("is_minipg", "1"); // String(1) 值为1，表示小程序支付；不传或值为2，表示公众账号内支付
        bizParamsMap.put("appid", ysProperties.getAppId()); // String(32) 当发起公众号支付时，值是微信公众平台基本配置中的AppID(应用ID)；当发起小程序支付时，值是对应小程序的AppID
        // 访问
        String result = this.postYsApi(url, "ysepay.online.weixin.pay", bizParamsMap, ysProperties.getPayNotifyUrl(), paymentType);
        log.info("Prepare paid result: {}", result);
        Map<String, Object> responseMap = JsonUtil.jsonToMap(result);
        Map<String, Object> infoMap = MapUtils.getMap(responseMap, "ysepay_online_weixin_pay_response");
//        ObjectMapper mapper = new ObjectMapper();
//        ResponsePaid responsePaid = mapper.convertValue(infoMap, ResponsePaid.class);
//        if (Objects.equals("10000", responsePaid.getCode())
//                && Objects.equals("Success", responsePaid.getMsg())
//                && Objects.equals("WAIT_BUYER_PAY", responsePaid.getTrade_status())) {
//            // 成功
//            return responsePaid;
//        } else {
//            return null;
//        }
        return infoMap;
    }

    // 查询支付
    public Map<String, Object> queryPayment(String outTradeNo, String shopDate, String tradeNo) throws Exception {
        String url = "https://search.ysepay.com/gateway.do";
        // 创建业务参数
        Map<String, Object> bizParamsMap = new HashMap<>();
        bizParamsMap.put("out_trade_no", outTradeNo); // String(1,32)	商户生成的订单号，生成规则前8位必须为交易日期，如20180525，范围跨度支持包含当天在内的前后一天，且只能由大小写英文字母、数字、下划线及横杠组成
        bizParamsMap.put("shopdate", shopDate); // String(8,8) 商户日期(该参数做交易与查询时需要一致) 该日期需在当日的前后一天时间范围之内
        bizParamsMap.put("trade_no", tradeNo); // String(1,30) 该交易在银盛支付系统中的交易流水号。
        // 访问
        String result = this.postYsApi(url, "ysepay.online.trade.query", bizParamsMap, null, 0);
        log.info("Query payment result: {}", result);
        Map<String, Object> responseMap = JsonUtil.jsonToMap(result);
        Map<String, Object> infoMap = MapUtils.getMap(responseMap, "ysepay_online_trade_query_response");
        return infoMap;
    }

    /**
     * 分账登记
     * @param outTradeNo
     * @param payeeUserCode
     * @param totalAmount
     * @param divList 分账明细
     * @return
     * @throws Exception
     */
    public Map<String, Object> divide(String outTradeNo, String payeeUserCode,
                                      String totalAmount, List<Map<String, Object>> divList) throws Exception {
        String url = "https://commonapi.ysepay.com/gateway.do";

        Map<String, Object> bizParamsMap = new HashMap<>();
        bizParamsMap.put("out_trade_no", outTradeNo);
        bizParamsMap.put("payee_usercode", payeeUserCode);
        bizParamsMap.put("total_amount", totalAmount);
        bizParamsMap.put("is_divistion", "01");
        bizParamsMap.put("is_again_division", "N");
        bizParamsMap.put("division_mode", "01");
        bizParamsMap.put("div_list", divList);

        String result = this.postYsApi(url, "ysepay.single.division.online.accept", bizParamsMap, ysProperties.getDivideNotifyUrl(), 0);
        Map<String, Object> responseMap = JsonUtil.jsonToMap(result);
        return responseMap;
    }

    /**
     * 主动查询分账结果
     * @param srcUserCode 原交易发起方
     * @param outTradeNo 原订单号
     * @return
     * @throws Exception
     */
    public Map<String, Object> queryDivide(String srcUserCode, String outTradeNo) throws Exception {
        String url = "https://commonapi.ysepay.com/gateway.do";
        Map<String, Object> bizParamMap = new HashMap<>();
        bizParamMap.put("src_usercode", ysProperties.getSellerId());
        bizParamMap.put("out_trade_no", outTradeNo);

        String result = this.postYsApi(url, "ysepay.single.division.online.query", bizParamMap, ysProperties.getDivideNotifyUrl(), 0);
        Map<String, Object> responseMap = JsonUtil.jsonToMap(result);
        return responseMap;
    }
    /**
     * 退款
     *
     * @param outTradeNo
     * @param shopDate
     * @param tradeNo
     * @param refundAmount
     * @param refundReason
     * @param outRequestNo
     * @return
     * @throws Exception
     */
    public Map<String, Object> divideRefund(String outTradeNo, String shopDate, String tradeNo, String refundAmount, String refundReason, String outRequestNo,
                                      List<Map<String, Object>> refundSplitInfo, List<Map<String, Object>> orderDivList) throws Exception {
        String url = "https://openapi.ysepay.com/gateway.do";
        // 创建业务参数
        Map<String, Object> bizParamsMap = new HashMap<>();
        bizParamsMap.put("out_trade_no", outTradeNo); // String(1,32) 商户生成的订单号，生成规则前8位必须为交易日期，如20180525，范围跨度支持包含当天在内的前后一天，且只能由大小写英文字母、数字、下划线及横杠组成
        bizParamsMap.put("shopdate", shopDate); // String(8,8) 商户日期(该参数做交易与查询时需要一致) 该日期需在当日的前后一天时间范围之内
        bizParamsMap.put("trade_no", tradeNo); // String(1，30) 银盛交易号
        bizParamsMap.put("refund_amount", refundAmount); // Number(10,2) 需要退款的金额，该金额不能大于订单金额,单位为元，支持两位小数，Number(10,2)指10位长度，2位精度
        bizParamsMap.put("refund_reason", refundReason); // String(1，50) 退款的原因说明，该参数最长为50个汉字。
        bizParamsMap.put("out_request_no", outRequestNo); // String(1，32) 标识一次退款请求，同一笔交易多次退款需要保证唯一。该参数支持汉字，最大长度为16个。用同一订单号同一退款流水号继续退款时，则会返回第一次退款的结果不会继续退款
        bizParamsMap.put("is_division", "01");
        bizParamsMap.put("refund_split_info", refundSplitInfo);
        bizParamsMap.put("ori_division_mode", "01");
        bizParamsMap.put("order_div_list", orderDivList);
        // 访问
        String result = this.postYsApi(url, "ysepay.online.trade.refund.split", bizParamsMap, ysProperties.getRefundNotifyUrl(), 2);
        Map<String, Object> responseMap = JsonUtil.jsonToMap(result);

        return responseMap;
    }

    /**
     * 查询退款
     *
     * @param tradeNo      String(1，30)，银盛平台交易流水号trade_no与out_trade_no 不能同时为空
     * @param outTradeNo   String(1，32)，订单支付时传入的商户订单号,和银盛交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取out_trade_no
     * @param outRequestNo String(1，32)，请求退款接口时，传入的退款请求订单号
     * @return
     * @throws Exception
     */
    public Map<String, Object> queryRefund(String tradeNo, String outTradeNo, String outRequestNo) throws Exception {
        String url = "https://openapi.ysepay.com/gateway.do";

        Map<String, Object> bizParamsMap = new HashMap<>();
        bizParamsMap.put("trade_no", tradeNo); // String(1，30) 银盛平台交易流水号trade_no与out_trade_no 不能同时为空
        bizParamsMap.put("out_trade_no", outTradeNo); // String(1，32) 订单支付时传入的商户订单号,和银盛交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取out_trade_no
        bizParamsMap.put("out_request_no", outRequestNo); // String(1，32) 请求退款接口时，传入的退款请求订单号

        String result = this.postYsApi(url, "ysepay.online.trade.refund.query", bizParamsMap, null, 0);
        Map<String, Object> resMap = JsonUtil.jsonToMap(result);
        return resMap;
    }

    /**
     * 担保交易 通知银盛发货
     * @param outTradeNo
     * @param shopDate
     * @param tradeNo
     * @return
     */
    public Map<String, Object> ysDeliver(String outTradeNo, String shopDate, String tradeNo) throws Exception {
        String url = "https://openapi.ysepay.com/gateway.do";
        // 创建业务参数
        Map<String, Object> bizParamsMap = new HashMap<>();
        bizParamsMap.put("out_trade_no", outTradeNo); // String(1，30) 银盛平台交易流水号trade_no与out_trade_no 不能同时为空
        bizParamsMap.put("shopdate", shopDate);
        bizParamsMap.put("trade_no", tradeNo);

        String result = this.postYsApi(url, "ysepay.online.trade.delivered", bizParamsMap, null, 0);
        Map<String, Object> resMap = JsonUtil.jsonToMap(result);
        return resMap;
    }

    public Map<String, Object> ysSign(String outTradeNo, String shopDate, String tradeNo) throws Exception {
        String url = "https://openapi.ysepay.com/gateway.do";
        // 创建业务参数
        Map<String, Object> bizParamsMap = new HashMap<>();
        bizParamsMap.put("out_trade_no", outTradeNo); // String(1，30) 银盛平台交易流水号trade_no与out_trade_no 不能同时为空
        bizParamsMap.put("shopdate", shopDate);
        bizParamsMap.put("trade_no", tradeNo);

        String result = this.postYsApi(url, "ysepay.online.trade.confirm", bizParamsMap, null, 0);
        Map<String, Object> resMap = JsonUtil.jsonToMap(result);
        return resMap;
    }



    /**
     * 获取上传附件token
     *
     * @return
     * @throws Exception
     */
    public String getUploadToken() throws Exception {
        String url = "https://register.ysepay.com:2443/register_gateway/gateway.do";
        // 访问
        String result = this.postYsApi(url, "ysepay.merchant.register.token.get", null, null, 0);
        if (log.isDebugEnabled()) {
            log.debug("Get upload token: [ {} ]", result);
        }
        ResGetUploadToken response = JsonUtil.jsonToObject(result, ResGetUploadToken.class);
        if (response.getCode() == 10000) {
            return response.getToken();
        } else {
            error("Get upload token error: [{}]", result);
            return null;
        }
    }

    /**
     * 进件上传文件
     *
     * @param token
     * @param picType
     * @param bytes
     * @return
     * @throws Exception
     */
    public boolean upload(String token, String picType, byte[] bytes) throws Exception {
        String url = "https://uploadApi.ysepay.com:2443/yspay-upload-service?method=upload";
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("superUsercode", ysProperties.getPartnerId());
        paramsMap.put("picType", picType);
        paramsMap.put("picFile", bytes);
        paramsMap.put("token", token);
        String result = okHttp.postBytes(url, paramsMap);
        ResUpload response = JsonUtil.jsonToObject(result, ResUpload.class);
        if (response.isSuccess()) {
            return true;
        } else {
            error("Upload error: [ {} ]", result);
            return false;
        }
    }

    /**
     * 进件
     * @param token
     * @param ysepayRegister
     * @return
     * @throws Exception
     */
    public ResRegister register(String token, YsepayRegister ysepayRegister) throws Exception {
        String url = "https://register.ysepay.com:2443/register_gateway/gateway.do";
        Map<String, Object> paramsMap = ysepayRegister.toMapWithKeyUnderline();
        // 上传文件token
        paramsMap.put("token", token);
        // 机构号
        paramsMap.put("org_no", ysProperties.getOrgNo());
        // 加密
        String key = StringUtils.leftPad(ysProperties.getPartnerId(), 8 , " ");
        paramsMap.put("cert_no", Base64Util.encode(DESUtil.encrypt(ysepayRegister.getCertNo().getBytes("UTF-8"), key)));
        paramsMap.put("legal_cert_no", Base64Util.encode(DESUtil.encrypt(ysepayRegister.getLegalCertNo().getBytes("UTF-8"), key)));
        // 进件
        String result = this.postYsApi(url, "ysepay.merchant.register.accept", paramsMap, ysProperties.getRegNotifyUrl(), 0);
        ResRegister resRegister = JsonUtil.jsonToObject(result, ResRegister.class);

        return resRegister;
    }

    /**
     * 进件查询
     * @param merchantNo
     * @return
     * @throws Exception
     */
    public String searchRegister(String merchantNo) throws Exception {
        String url = "https://register.ysepay.com:2443/register_gateway/gateway.do";
        // 创建业务参数
        Map<String, Object> bizParamsMap = new HashMap<>();
        bizParamsMap.put("merchant_no", merchantNo); // String(1，30) 银盛平台交易流水号trade_no与out_trade_no 不能同时为空
        // 访问
        String result = this.postYsApi(url, "ysepay.merchant.register.query", bizParamsMap, null, 0);
        log.info("Prepare paid result: {}", result);
        return result;
    }

    public String searchRegisterByUserCode(String userCode) throws Exception {
        String url = "https://register.ysepay.com:2443/register_gateway/gateway.do";
        // 创建业务参数
        Map<String, Object> bizParamsMap = new HashMap<>();
        bizParamsMap.put("usercode", userCode); // String(1，30) 银盛平台交易流水号trade_no与out_trade_no 不能同时为空
        // 访问
        String result = this.postYsApi(url, "ysepay.merchant.register.query", bizParamsMap, null, 0);
        log.info("Prepare paid result: {}", result);
        return result;
    }

    /**
     * 访问YsApi基础函数
     *
     * @param url
     * @param method
     * @param bizContent
     * @return
     * @throws Exception
     */
    public String postYsApi(String url, String method, Map<String, Object> bizContent, String notifyUrl, Integer paymentType) throws Exception {
        Map<String, String> paramsMap = new HashMap<String, String>();
        // 基础参数
        paramsMap.put("method", method); // String(128) 接口名称。
        paramsMap.put("partner_id", ysProperties.getPartnerId()); // String(20) 商户在银盛支付平台开设的用户号[商户号]
        paramsMap.put("timestamp", DateUtil.formatNow("yyyy-MM-dd HH:mm:ss")); // String(19) 发送请求的时间，格式"yyyy-MM-dd HHmmss"
        paramsMap.put("charset", "UTF-8"); // String(10) 商户网站使用的编码格式，如utf-8、gbk、gb2312等。
        paramsMap.put("sign_type", "RSA"); // String(256) RSA签名字符串，再用Base64编码
        // 回调
        if (!Strings.isNullOrEmpty(notifyUrl)) {
            paramsMap.put("notify_url", notifyUrl);
        } else {
            paramsMap.put("notify_url", "");
        }

        paramsMap.put("version", "3.0"); // String(3) 接口版本
        if (paymentType == 1)
            paramsMap.put("tran_type", "1"); // String(1) 交易类型，说明：1或者空：即时到账，2：担保交易
        else if (paymentType == 2)
            paramsMap.put("tran_type", "2"); // String(1) 交易类型，说明：1或者空：即时到账，2：担保交易
        // 业务参数
        if (bizContent != null) {
            paramsMap.put("biz_content", JsonUtil.toJson(bizContent));
        } else {
            paramsMap.put("biz_content", "{}");
        }
        // 准备参数签名
        String signStr = SignUtil.getSignContent(paramsMap);
        // 获取证书流
        ByteArrayInputStream bis = new ByteArrayInputStream(Base64Util.decode2Bytes(ysProperties.getMyCert()));
        // 签名
        String rsaSign = SignUtil.rsaSign(signStr, "UTF-8", bis, ysProperties.getMyCertPwd());
        paramsMap.put("sign", rsaSign);
        // 访问
        String result = okHttp.postMap(url, paramsMap);
        return result;
    }

    /**
     * 写错误日志
     *
     * @param format
     * @param args
     */
    private void error(String format, Object... args) {
        MDC.put(Const.TRACE_ID_KEY, "ERR_YS");
        log.error(format, args);
        MDC.remove(Const.TRACE_ID_KEY);
    }



//    public static boolean rsaCheckContent(PublicKey pubKey, String content, String sign, String charset) throws Exception {
//        System.out.println("进入验证签名方法: content[" + content + "], sign[" + sign + "], charset[" + charset + "]");
//        boolean bFlag = false;
//        try {
//            java.security.Signature signetcheck = java.security.Signature.getInstance(“SHA1WithRSA”);
//            signetcheck.initVerify(pubKey);
//            signetcheck.update(content.getBytes(charset));
//            if (signetcheck.verify(Base64.decodeBase64(sign.getBytes(charset)))) {
//                bFlag = true;
//            }
//            System.out.println("进入验证签名方法: content[" + content + "], sign[" + sign + "], charset[" + charset + "], result[" + bFlag + "]");
//        } catch (Exception e) {
//            System.out.println("验证签名异常" + ": content[" + content + "], sign[" + sign + "], charset[" + charset + "]");
//            throw new Exception("验证签名异常");
//        }
//        return bFlag;
//    }
}
